﻿using System;
using rentit03_server.Database;

namespace rentit03_server.Business
{
    /// <summary>
    /// This class is responsible for handling comments.
    /// If you read a media through the mediahandler, you will also get all the related comments.
    /// </summary>
    public static class CommentHandler
    {
        private static readonly DataAccessObject Dao = new DataAccessObject();

        /// <summary>
        /// Create a new comment.
        /// </summary>
        /// <param name="rentitComment">New comment</param>
        /// <param name="sessionId">Session id</param>
        /// <returns>The id of the newly created comment</returns>
        public static int CreateComment(RentitComment rentitComment, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.CreateComment, sessionId, true);

            var comment = rentitComment.ToComment();
            IsValidComment(comment);

            return Dao.CreateComment(comment);
        }

        /// <summary>
        /// Read a comment from a comment id.
        /// </summary>
        /// <param name="commentId">Id of the comment you want to read</param>
        /// <param name="sessionId">Session id</param>
        /// <returns>The comment</returns>
        public static RentitComment ReadComment(int commentId, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.ReadComment, sessionId, true);

            var c = Dao.ReadComment(commentId);

            if (c == null)
                throw new CommentHandlerException(ReturnStatus.CommentInvalidCommentId, "The comment id didn't match any comment");

            return c.ToRentitComment();
        }

        /// <summary>
        /// Edit a comment in the database. The existing comment will be replaced by the updated comment.
        /// </summary>
        /// <param name="commentId">Id of the comment you want to edit</param>
        /// <param name="rentitComment">Updated comment</param>
        /// <param name="sessionId">Session id</param>
        public static void EditComment(int commentId, RentitComment rentitComment, string sessionId)
        {
            var userId = ReadComment(commentId, sessionId).UserId;
            SessionHandler.UserAccess(PermissionEnum.EditComment, sessionId, true, userId);

            var c = rentitComment.ToComment();
            if (!Dao.EditComment(c, commentId))
                throw new CommentHandlerException(ReturnStatus.CommentInvalidCommentId, "The comment id didn't match any comment");
        }

        /// <summary>
        /// Delete a comment.
        /// </summary>
        /// <param name="commentId">Id of the comment you want to delete</param>
        /// <param name="sessionId">Session id</param>
        public static int DeleteComment(int commentId, string sessionId)
        {
            var comment = ReadComment(commentId, sessionId);
            SessionHandler.UserAccess(PermissionEnum.DeleteComment, sessionId, true, comment.UserId);

            if (!Dao.DeleteComment(commentId))
                throw new CommentHandlerException(ReturnStatus.CommentInvalidCommentId, "The comment id didn't match any comment");

            return comment.MediaId;
        }

        /// <summary>
        /// Check if a comment is valid.
        /// Media id must non be negative.
        /// User id must be non negative.
        /// The body of the comment must be set.
        /// Throws an exception if the requirements above is not met.
        /// </summary>
        /// <param name="comment">The comment</param>
        private static void IsValidComment(Comment comment)
        {
            if (comment.MediaId < 0)
                throw new CommentHandlerException(ReturnStatus.CommentValidateMediaId, "Media id must be non negative");

            if (comment.UserId < 0)
                throw new CommentHandlerException(ReturnStatus.CommentValidateUserId, "User id must be non negative");

            if (String.IsNullOrWhiteSpace(comment.Text))
                throw new CommentHandlerException(ReturnStatus.CommentValidateCommentBody, "Comment must contain a comment body");
        }

        /// <summary>
        /// Convert a RentitComment to a Comment that can be understood by the data layer.
        /// </summary>
        /// <param name="c">A RentitComment</param>
        /// <returns>A Comment</returns>
        private static Comment ToComment(this RentitComment c)
        {
            var comment = new Comment
                       {
                           Id = c.Id,
                           MediaId = c.MediaId,
                           Text = c.Comment,
                           UserId = c.UserId,
                       };

            DateTime cre;
            if (DateTime.TryParse(c.Created, out cre))
                comment.Created = cre;

            DateTime upd;
            if (DateTime.TryParse(c.Updated, out upd))
                comment.Updated = upd;

            return comment;
        }

        /// <summary>
        /// Convert a Comment from the data layer to a RentitComment.
        /// </summary>
        /// <param name="comment">A Comment</param>
        /// <returns>A RentitComment</returns>
        public static RentitComment ToRentitComment(this Comment comment)
        {
            return new RentitComment
            {
                Id = comment.Id,
                MediaId = comment.MediaId,
                Comment = comment.Text,
                UserId = comment.UserId,
                UserName = (comment.User != null ? comment.User.Name : String.Empty),
                Created = comment.Created.ToString("o"),
                Updated = comment.Updated.ToString("o")
            };
        }
    }

    /// <summary>
    /// Custom exception used by the CommentHandler
    /// </summary>
    public class CommentHandlerException : RentitException
    {
        public CommentHandlerException(ReturnStatus status, string message) : base(status, message) { }
    }

    /// <summary>
    /// Representation of a Comment
    /// </summary>
    public struct RentitComment
    {
        public int Id { get; set; }

        public int MediaId { get; set; }

        public string Comment { get; set; }

        public int UserId { get; set; }

        public string UserName { get; set; }

        public string Created { get; set; }

        public string Updated { get; set; }
    }
}